local base = _G
local my   = {}
my.socket  = null
my.metat   = { __index = {} }


function luav()
    print(_VERSION)
end

function attrdir(path)
    for file in my.lfs.dir(path) do
        if file ~= "." and file ~= ".." then
            local f = path..'/'..file
            local attr = my.lfs.attributes(f)
            if attr.mode == "directory" then
                attrdir(f)
            else
                print(f)
            end
        end
    end
end

function srequest(u, b)
    local t = {}
    local reqt = {
        url = u,
        sink = sink_table(t)
    }
    if b then
        reqt.source = source_string(b)
        reqt.headers = {
            ["content-length"] = string.len(b),
            ["content-type"] = "application/x-www-form-urlencoded"
        }
        reqt.method = "POST"
    end
    local code, headers, status = my.socket.skip(1, trequest(reqt))
    return table.concat(t), code, headers, status
end

function connect(address, port, laddress, lport)
    local sock, err = my.socket.tcp()
    if not sock then return nil, err end
    if laddress then
        local res, err = sock:bind(laddress, lport, -1)
        if not res then return nil, err end
    end
    local res, err = sock:connect(address, port)
    if not res then return nil, err end
    return sock
end

function bind(host, port, backlog)
    local sock, err = my.socket.tcp()
    if not sock then return nil, err end
    sock:setoption("reuseaddr", true)
    local res, err = sock:bind(host, port)
    if not res then return nil, err end
    res, err = sock:listen(backlog)
    if not res then return nil, err end
    return sock
end

function source_string(s)
    if s then
        local i = 1
        return function()
            local chunk = string.sub(s, i, i+2048-1)
            i = i + 2048
            if chunk ~= "" then return chunk
            else return nil end
        end
    else return source.empty() end
end

function sink_table(t)
    t = t or {}
    local f = function(chunk, err)
        if chunk then table.insert(t, chunk) end
        return 1
    end
    return f, t
end

function source_rewind(src)
    base.assert(src)
    local t = {}
    return function(chunk)
        if not chunk then
            chunk = table.remove(t)
            if not chunk then return src()
            else return chunk end
        else
            table.insert(t, chunk)
        end
    end
end

function pump_step(src, snk)
    local chunk, src_err = src()
    local ret, snk_err = snk(chunk, src_err)
    if chunk and ret then return 1
    else return nil, src_err or snk_err end
end

function pump_all(src, snk, step)
    base.assert(src and snk)
    step = step or pump_step
    while true do
        local ret, err = step(src, snk)

        if not ret then
            if err then return nil, err
            else return 1 end
        end
    end
end

function base64decode(str)
    return str
end

function choose(tables)
    return function(name, opt1, opt2)
        if base.type(name) ~= "string" then
            name, opt1, opt2 = "default", name, opt1
        end
        local f = tables[name or "nil"]
        if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
        else return f(opt1, opt2) end
    end
end

function get_sinkt()
    local sinkt = {}
    sinkt["close-when-done"] = function(sock)
        return base.setmetatable({
            getfd = function() return sock:getfd() end,
            dirty = function() return sock:dirty() end
        }, {
            __call = function(self, chunk, err)
                if not chunk then
                    sock:close()
                    return 1
                else return sock:send(chunk) end
            end
        })
    end
    sinkt["keep-open"] = function(sock)
        return base.setmetatable({
            getfd = function() return sock:getfd() end,
            dirty = function() return sock:dirty() end
        }, {
            __call = function(self, chunk, err)
                if chunk then return sock:send(chunk)
                else return 1 end
            end
        })
    end
    sinkt["http-chunked"] = function(sock)
        return base.setmetatable({
            getfd = function() return sock:getfd() end,
            dirty = function() return sock:dirty() end
        }, {
            __call = function(self, chunk, err)
                if not chunk then return sock:send("0\r\n\r\n") end
                local size = string.format("%X\r\n", string.len(chunk))
                return sock:send(size ..  chunk .. "\r\n")
            end
        })
    end
    sinkt["default"] = sinkt["keep-open"]
    return choose(sinkt)
end

function get_sourcet()
    local sourcet = {}
    sourcet["by-length"] = function(sock, length)
        return base.setmetatable({
            getfd = function() return sock:getfd() end,
            dirty = function() return sock:dirty() end
        }, {
            __call = function()
                if length <= 0 then return nil end
                local size = math.min(2048, length)
                local chunk, err = sock:receive(size)
                if err then return nil, err end
                length = length - string.len(chunk)
                return chunk
            end
        })
    end
    sourcet["until-closed"] = function(sock)
        local done
        return base.setmetatable({
            getfd = function() return sock:getfd() end,
            dirty = function() return sock:dirty() end
        }, {
            __call = function()
                if done then return nil end
                local chunk, err, partial = sock:receive(2048)

                if not err then return chunk
                elseif err == "closed" then
                    sock:close()
                    done = 1
                    return partial
                else return nil, err end
            end
        })
    end
    sourcet["http-chunked"] = function(sock, headers)
        return base.setmetatable({
            getfd = function() return sock:getfd() end,
            dirty = function() return sock:dirty() end
        }, {
            __call = function()
                local line, err = sock:receive()
                if err then return nil, err end
                local size = base.tonumber(string.gsub(line, ";.*", ""), 16)
                if not size then return nil, "invalid chunk size" end
                if size > 0 then
                    local chunk, err, part = sock:receive(size)
                    if chunk then sock:receive() end
                    return chunk, err
                else
                    headers, err = receiveheaders(sock, headers)
                    if not headers then return nil, err end
                end
            end
        })
    end
    sourcet["default"] = sourcet["until-closed"]
    return choose(sourcet)
end

function url_parse(url, default)
    local parsed = {}
    for i,v in base.pairs(default or parsed) do parsed[i] = v end
    if not url or url == "" then return nil, "invalid url" end
    url = string.gsub(url, "#(.*)$", function(f)
        parsed.fragment = f
        return ""
    end)
    url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
        function(s) parsed.scheme = s; return "" end)
    url = string.gsub(url, "^//([^/]*)", function(n)
        parsed.authority = n
        return ""
    end)
    url = string.gsub(url, "%?(.*)", function(q)
        parsed.query = q
        return ""
    end)
    url = string.gsub(url, "%;(.*)", function(p)
        parsed.params = p
        return ""
    end)
    if url ~= "" then 
        parsed.path = url 
    end
    local authority = parsed.authority
    if not authority then 
        return parsed 
    end
    authority = string.gsub(authority,"^([^@]*)@", function(u) 
        parsed.userinfo = u
        return "" 
    end)
    authority = string.gsub(authority, ":([^:]*)$", function(p) 
        parsed.port = p
        return "" 
    end)
    if authority ~= "" then 
        parsed.host = authority 
    end
    local userinfo = parsed.userinfo
    if not userinfo then 
        return parsed 
    end
    userinfo = string.gsub(userinfo, ":([^:]*)$", function(p) 
        parsed.password = p
        return ""
    end)
    parsed.user = userinfo
    return parsed
end

function unescape(s)
    return string.gsub(s, "%%(%x%x)", function(hex)
        return string.char(base.tonumber(hex, 16))
    end)
end

function make_set(t)
    local s = {}
    for i,v in base.ipairs(t) do
        s[t[i]] = 1
    end
    return s
end

function protect_segment(s)
    local segment_set = make_set {
        "-", "_", ".", "!", "~", "*", "'", "(",
        ")", ":", "@", "&", "=", "+", "$", ",",
    }
    return string.gsub(s, "([^A-Za-z0-9_])", function (c)
        if segment_set[c] then 
            return c
        else 
            return string.format("%%%02x", string.byte(c)) 
        end
    end)
end

function parse_path(path)
    local parsed = {}
    path = path or ""
    string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end)
    for i = 1, #parsed do
        parsed[i] = unescape(parsed[i])
    end
    if string.sub(path, 1, 1) == "/" then 
        parsed.is_absolute = 1 
    end
    if string.sub(path, -1, -1) == "/" then 
        parsed.is_directory = 1 
    end
    return parsed
end

function build_path(parsed, unsafe)
    local path = ""
    local n = #parsed
    if unsafe then
        for i = 1, n-1 do
            path = path .. parsed[i]
            path = path .. "/"
        end
        if n > 0 then
            path = path .. parsed[n]
            if parsed.is_directory then 
                path = path .. "/" 
            end
        end
    else
        for i = 1, n-1 do
            path = path .. protect_segment(parsed[i])
            path = path .. "/"
        end
        if n > 0 then
            path = path .. protect_segment(parsed[n])
            if parsed.is_directory then 
                path = path .. "/" 
            end
        end
    end
    if parsed.is_absolute then 
        path = "/" .. path 
    end
    return path
end

function url_build(parsed)
    local ppath = parse_path(parsed.path or "")
    local url = build_path(ppath)
    if parsed.params then 
        url = url .. ";" .. parsed.params 
    end
    if parsed.query then 
        url = url .. "?" .. parsed.query 
    end
    local authority = parsed.authority
    if parsed.host then
        authority = parsed.host
        if parsed.port then 
            authority = authority .. ":" .. parsed.port 
        end
        local userinfo = parsed.userinfo
        if parsed.user then
            userinfo = parsed.user
            if parsed.password then
                userinfo = userinfo .. ":" .. parsed.password
            end
        end
        if userinfo then 
            authority = userinfo .. "@" .. authority 
        end
    end
    if authority then 
        url = "//" .. authority .. url 
    end
    if parsed.scheme then 
        url = parsed.scheme .. ":" .. url 
    end
    if parsed.fragment then 
        url = url .. "#" .. parsed.fragment 
    end
    return url
end

function adjusturi(reqt)
    local u = reqt
    if not reqt.proxy and not PROXY then
        u = {
           path = my.socket.try(reqt.path, "invalid path 'nil'"),
           params = reqt.params,
           query = reqt.query,
           fragment = reqt.fragment
        }
    end
    return url_build(u)
end

function adjustproxy(reqt)
    local proxy = reqt.proxy or PROXY
    if proxy then
        proxy = url_parse(proxy)
        return proxy.host, proxy.port or 3128
    else
        return reqt.host, reqt.port
    end
end

function adjustheaders(reqt)
    local lower = {
        ["user-agent"] = my.socket._VERSION,
        ["host"] = reqt.host,
        ["connection"] = "close, TE",
        ["te"] = "trailers"
    }
    if reqt.user and reqt.password then
        lower["authorization"] =  "Basic " ..  (base64decode(reqt.user .. ":" .. reqt.password))
    end
    for i,v in base.pairs(reqt.headers or lower) do
        lower[string.lower(i)] = v
    end
    return lower
end

function adjustrequest(reqt)
    local default = {
        host = "",
        port = 80,
        path ="/",
        scheme = "http"
    }
    local nreqt = reqt.url and url_parse(reqt.url, default) or {}
    for i,v in base.pairs(reqt) do 
        nreqt[i] = v 
    end
    if nreqt.port == "" then 
        nreqt.port = 80 
    end
    my.socket.try(nreqt.host and nreqt.host ~= "", "invalid host '" .. base.tostring(nreqt.host) .. "'")
    nreqt.uri = reqt.uri or adjusturi(nreqt)
    nreqt.host, nreqt.port = adjustproxy(nreqt)
    nreqt.headers = adjustheaders(nreqt)
    return nreqt
end

function receiveheaders(sock, headers)
    local line, name, value, err
    headers = headers or {}
    line, err = sock:receive()
    if err then return nil, err end
    while line ~= "" do
        name, value = my.socket.skip(2, string.find(line, "^(.-):%s*(.*)"))
        if not (name and value) then return nil, "malformed reponse headers" end
        name = string.lower(name)
        line, err  = sock:receive()
        if err then return nil, err end
        while string.find(line, "^%s") do
            value = value .. line
            line = sock:receive()
            if err then return nil, err end
        end
        if headers[name] then headers[name] = headers[name] .. ", " .. value
        else headers[name] = value end
    end
    return headers
end

function open(host, port, create)
    local c = my.socket.try((create or my.socket.tcp)())
    local h = base.setmetatable({ c = c }, my.metat)
    h.try = my.socket.newtry(function() h:close() end)
    h.try(c:settimeout(60))
    h.try(c:connect(host, port or 80))
    return h
end

function my.metat.__index:sendrequestline(method, uri)
    local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri)
    return self.try(self.c:send(reqline))
end

function my.metat.__index:sendheaders(headers)
    local h = "\r\n"
    for i, v in base.pairs(headers) do
        h = i .. ": " .. v .. "\r\n" .. h
    end
    self.try(self.c:send(h))
    return 1
end

function my.metat.__index:sendbody(headers, source, step)
    source = source or nil
    step = step or pump_step
    local mode = "http-chunked"
    if headers["content-length"] then mode = "keep-open" end
    return self.try(pump_all(source, my.socket.sink(mode, self.c), step))
end

function my.metat.__index:receivestatusline()
    local status = self.try(self.c:receive(5))
    if status ~= "HTTP/" then return nil, status end
    status = self.try(self.c:receive("*l", status))
    local code = my.socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)"))
    return self.try(base.tonumber(code), status)
end

function my.metat.__index:receiveheaders()
    return self.try(receiveheaders(self.c))
end

function my.metat.__index:receivebody(headers, sink, step)
    sink = sink or null
    step = step or pump_step
    local length = base.tonumber(headers["content-length"])
    local t = headers["transfer-encoding"]
    local mode = "default"
    if t and t ~= "identity" then mode = "http-chunked"
    elseif base.tonumber(headers["content-length"]) then mode = "by-length" end
    return self.try(pump_all(my.socket.source(mode, self.c, length), sink, step))
end

function my.metat.__index:receive09body(status, sink, step)
    local source = source_rewind(my.socket.source("until-closed", self.c))
    source(status)
    return self.try(pump_all(source, sink, step))
end

function my.metat.__index:close()
    return self.c:close()
end

function absolute_path(base_path, relative_path)
    if string.sub(relative_path, 1, 1) == "/" then return relative_path end
    local path = string.gsub(base_path, "[^/]*$", "")
    path = path .. relative_path
    path = string.gsub(path, "([^/]*%./)", function (s)
        if s ~= "./" then return s else return "" end
    end)
    path = string.gsub(path, "/%.$", "/")
    local reduced
    while reduced ~= path do
        reduced = path
        path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
            if s ~= "../../" then return "" else return s end
        end)
    end
    path = string.gsub(reduced, "([^/]*/%.%.)$", function (s)
        if s ~= "../.." then return "" else return s end
    end)
    return path
end

function absolute(base_url, relative_url)
    if base.type(base_url) == "table" then
        base_parsed = base_url
        base_url = url_build(base_parsed)
    else
        base_parsed = url_parse(base_url)
    end
    local relative_parsed = url_parse(relative_url)
    if not base_parsed then return relative_url
    elseif not relative_parsed then return base_url
    elseif relative_parsed.scheme then return relative_url
    else
        relative_parsed.scheme = base_parsed.scheme
        if not relative_parsed.authority then
            relative_parsed.authority = base_parsed.authority
            if not relative_parsed.path then
                relative_parsed.path = base_parsed.path
                if not relative_parsed.params then
                    relative_parsed.params = base_parsed.params
                    if not relative_parsed.query then
                        relative_parsed.query = base_parsed.query
                    end
                end
            else    
                relative_parsed.path = absolute_path(base_parsed.path or "", relative_parsed.path)
            end
        end
        return url_build(relative_parsed)
    end
end


function shouldredirect(reqt, code, headers)
    return headers.location and
           string.gsub(headers.location, "%s", "") ~= "" and
           (reqt.redirect ~= false) and
           (code == 301 or code == 302) and
           (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") and
           (not reqt.nredirects or reqt.nredirects < 5)
end

function shouldreceivebody(reqt, code)
    if reqt.method == "HEAD" then return nil end
    if code == 204 or code == 304 then return nil end
    if code >= 100 and code < 200 then return nil end
    return 1
end

function tredirect(reqt, location)
    local result, code, headers, status = trequest {
        url = absolute(reqt.url, location),
        source = reqt.source,
        sink = reqt.sink,
        headers = reqt.headers,
        proxy = reqt.proxy, 
        nredirects = (reqt.nredirects or 0) + 1,
        create = reqt.create
    }
    headers = headers or {}
    headers.location = headers.location or location
    return result, code, headers, status
end

function trequest(reqt)
    local nreqt = adjustrequest(reqt)
    local h = open(nreqt.host, nreqt.port, nreqt.create)
    h:sendrequestline(nreqt.method, nreqt.uri)
    h:sendheaders(nreqt.headers)
    if nreqt.source then
        h:sendbody(nreqt.headers, nreqt.source, nreqt.step) 
    end
    local code, status = h:receivestatusline()
    if not code then
        h:receive09body(status, nreqt.sink, nreqt.step)
        return 1, 200
    end
    local headers
    while code == 100 do 
        headers = h:receiveheaders()
        code, status = h:receivestatusline()
    end
    headers = h:receiveheaders()
    if shouldredirect(nreqt, code, headers) and not nreqt.source then
        h:close()
        return tredirect(reqt, headers.location)
    end
    if shouldreceivebody(nreqt, code) then
        h:receivebody(headers, nreqt.sink, nreqt.step)
    end
    h:close()
    return 1, code, headers, status
end

function new_http(game,channel,model)
    local json = require("json")
    my.socket  = require("socket.core")
    my.socket.try     = my.socket.newtry()
    my.socket.sink    = get_sinkt() 
    my.socket.source  = get_sourcet()
    --my.socket.connect = connect
    --my.socket.bind    = bind
    request = my.socket.protect(function(reqt, body)
        if base.type(reqt) == "string" then 
            return srequest(reqt, body)
        else 
            return trequest(reqt) 
        end
    end)
    local res = {}

    --json请求
    local reqbody = json.encode({game=game,channel=channel,model=model})
    local body, code, headers, status = request{
        method = "POST",
        url = "http://192.168.50.146:8080/get",
        source = source_string(reqbody),
        headers = {
                    ["content-type"] = "application/json",
                    ["content-length"] = string.len(reqbody)
                },
        sink = sink_table(res)
    }

    --表单请求
    --local body, code, headers, status = request{
    --    method = "POST",
    --    url = "http://192.168.50.146:8080/get",
    --    body = {
    --        game=game,
    --        model=model,
    --        channel=channel
    --    },
    --    sink = sink_table(res)
    --}
    print(#status,#headers,#res)
    print(table.concat(headers))
    print(table.concat(res))
end



new_http("谭松韵","测试","测试")